/*
 *  objects.h
 *  joosVM
 *
 *  Created by Smári Waage on 11.6.2009.
 *  Copyright 2009 smari.org. All rights reserved.
 *
 *  This program is free software; you can redistribute it and/or
 *  modify it under the terms of the GNU General Public License
 *  as published by the Free Software Foundation; either version 2
 *  of the License, or (at your option) any later version.
 *  
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *  
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA. *
 */

#ifndef objects___ 
#define objects___ 

#include "objects-type.h"
#include <math.h>
#include "xtrapbits.h"
//#define True true
//#define False false

/*
 * Frame - Stackframe..
 *
 * |	+Array				|
 * +------------------------+
 * |		local var		| taged pointer, to static array, both outer var, local var, parameters, first slot is this(if its not static), does not allow for 
 * +------------------------+
 * |		type			| //tagged pointer to a the type/class
 * +------------------------+
 * |		code			| //tagged pointer to a byteCodeArray/ should be the method
 * +------------------------+
 * |		pc				| //real 32bit int
 * +------------------------+
 * |		sp				| //
 * +------------------------+
 * |		prev fp			| //tagged pointer, just easy to be able to find it again t
 * +------------------------+ 
 * |	op_stack			| 
 * |		.				|
 * |		.				| //specified length
 * |		.				|
 * +------------------------+
 * |	flow tagging		|
 * |		.				|
 * +------------------------+
 */
class Frame : public Array {
	//static const int kLengthOffset = HeapObject::kHeaderSize;
	
	static const int kLocalsOffset = Array::kHeaderSize;
	static const int kTypeOffset = kLocalsOffset + kPointerSize;
	static const int kMethodOffset = kTypeOffset + kPointerSize;
	static const int kCodeOffset = kMethodOffset + kPointerSize;
	static const int kPCOffset = kCodeOffset + kPointerSize;
	static const int kSpOffset = kPCOffset + kPointerSize;
	static const int kPrevFpOffset = kSpOffset + kPointerSize;
	static const int kOperandStackOffset = kPrevFpOffset + kPointerSize;
	
	static const int kSpStartPositionOffset = kOperandStackOffset - k2PointerSize; //start one below

public:
	//static const int kHeaderSize = kPrevFpOffset + kPointerSize;
	
	static Frame* Initialize(JMethod* method){
		int length = method->getMaxStackSize();
		
		int len = length * k2PointerSize;
		int codearraylength = method->getCode()->array_length();
		Frame* f = (Frame*)HeapObject::allocHeapObject(kOperandStackOffset + len 
													   + ((int)ceil(codearraylength/kPointerSize))*kPointerSize);//make room for tagging for the flow
		f->setInternalType(Smi::fromInt(IType_Frame));
		f->setVariableLength(len);
		f->setPC(0);
		
		//place the pointer before the firstfield
		f->setSp((Pointer*)f->address()+ kSpStartPositionOffset);
		
		f->setCode(method->getCode());
		f->setType(method->getContainingType());
		f->setMethod(method);
		return f;
	}
	
	TaggedArray* getLocals(){ return (TaggedArray*)field(kLocalsOffset);}
	void setLocals(TaggedArray* locals){ setField(kLocalsOffset, locals);}
	
	JType* getType(){ return (JType*)field(kTypeOffset);}
	void setType(JType* type){ setField(kTypeOffset, type);}
	
	JMethod* getMethod(){ return (JMethod*)field(kMethodOffset);}
	void setMethod(JMethod* met){ setField(kMethodOffset, met);}
	
	ByteArray* getCode(){ return (ByteArray*) field(kCodeOffset);}
	void setCode(ByteArray* code){ setField(kCodeOffset, code);}
	
	int getPC(){return ((Smi*)field(kPCOffset))->value();}
	void setPC(int pc){ setField(kPCOffset,Smi::fromInt( pc));}
	
	Frame* getprevFP(){ return (Frame*)field(kPrevFpOffset);}
	void setprevFP(Frame* fp){ setField(kPrevFpOffset, fp);}
	
	
	Pointer* sp() { return field(kSpOffset); }
	void setSp(Pointer* sp) { setField(kSpOffset, sp); }

	//c
	void incSp() {
		//Check overflow and signal error
		Pointer* p = sp();
		if(p + kPointerSize >= address() + kOperandStackOffset + getVariableLength() ){//frame_size()){
			printf("\nStackOverflow size:%i %p %p\n",getVariableLength(),sp() + kPointerSize, address() + frame_size() ); exit(1);
		}
		setSp(p + k2PointerSize);
	}
	Pointer* pop(){ //decSp(){
		//should check for .. e hum ..underflow.
		Pointer** p = (Pointer**)sp();
		if((Pointer*)p <= address() + kSpStartPositionOffset){printf("StackUnderflow");exit(1);}
		setSp((Pointer*)p - k2PointerSize);	
		return *p;
	}
	
#if debugout	
	void printFrame(){
		ddout("Print frame\n");
		for(int i=kOperandStackOffset; i+address() <= sp() && sp() >= kOperandStackOffset +address();i+=k2PointerSize){
			Pointer* tmp = field(i);
			ddout("tag:%p %p\n",field(i-kPointerSize),tmp);
		}
	}
#endif
	
	void flag_flow(int pos, bool flag){
		UByteP p = (UByteP)address() + kOperandStackOffset + getVariableLength();
		flag?BitTrue(p,pos):BitFalse(p,pos);
	}
	bool chk_flow(int pos){
		UByteP p = (UByteP)address() + kOperandStackOffset + getVariableLength();
		return BitValue(p,pos);
	}
	
	int frame_size(){ 
#if CONTRACTS
		if(!getCode())
			return kOperandStackOffset + getVariableLength();
		else
			return kOperandStackOffset + getVariableLength() + (((int) ceil( getCode()->array_length()) /kPointerSize)*kPointerSize);
#else
		return kOperandStackOffset + getVariableLength() + ((int)ceil((getCode()->array_length())/kPointerSize)*kPointerSize);
#endif		
	}
	
	void frame_scan(Pointer** nHP){
		dout(debug_gc,"scan frame %p ",address());
		//length, is there, its a smi
		//dout(1,"%p %s\n",getLocals(),getMethod()->getName()->str())
		
		setField(kLocalsOffset,getLocals()->forward(nHP));
		HeapObject* b = getType();
		Pointer* t = b->forward(nHP);
		setField(kTypeOffset,t);
		setField(kMethodOffset, getMethod()->forward(nHP));
		setField(kCodeOffset,getCode()->forward(nHP));
		//PC is there
		
		//fix the sp, previous forward wrote the sp offset, generate new address base+offset = sp.
		int offr = (int)sp();
		Pointer* nsp = address() + offr;
		setField(kSpOffset,nsp);
		dout(debug_gc,"new sp %p offset %i \n",offr,sp());
		
		dout(debug_gc,"prev fp %p ",getprevFP());
		if (getprevFP() != NULL) {
			//write the offset to the sp place, so we can restore the sp when it is moved.
			int off = getprevFP()->sp() - (Pointer*)getprevFP()->address();
			getprevFP()->setSp((Pointer*)off);
			setField(kPrevFpOffset, getprevFP()->forward(nHP));
			dout(debug_gc,"new prevfp %p offs %i",getprevFP(),off) 
		}
		
		//bool bb[getVariableLength()/k2PointerSize];
		
		int end = (int)sp()-(int)address();
		for(int i=kOperandStackOffset + kPointerSize;i<= end+kPointerSize;i+=k2PointerSize){
			
			Pointer* c = field(i);
			if((int)c == 1){
				HeapObject* p = (HeapObject*)field(i -kPointerSize);
				setField(i-kPointerSize, (HeapObject*)p->forward(nHP));
			}
		}
		
		dout(debug_gc,"\n%p %p %p %p %p\n",getLocals(),getType(),getCode(),sp(),getprevFP())
		
		//The new plan is to not collect the operation stack, I can get the info about what locals I have to collect when I push it on 
		//just have to note new refs somewhere.,or rather just make a tagging field based on the size of the operation stack
		
		//each object on the stack will get an extra footer, so for each int well push a double... must be the most simple or stubit so its in-line with the design criteria
	}
};  


/*
 * Stack
 *
 * |	+HeapObject			|
 * +------------------------+
 * |		threadId		| 
 * +------------------------+
 * |		fp				| //current frame.
 * +------------------------+
 */

class Stack : public HeapObject {
	
	// Layout description.
	static const int kThreadIdOffset = HeapObject::kHeaderSize;
	static const int kFramePointerOffset = kThreadIdOffset + kPointerSize;
		
public:
	
	static const int kHeaderSize = kFramePointerOffset +kPointerSize;  //kStackOffset + kPointerSize;
	static Stack* Initialize(int thId){
		Stack* stack = (Stack*) HeapObject::allocHeapObject(Stack::kHeaderSize );
		stack->setInternalType(Smi::fromInt(IType_Stack));
		stack->setThreadId(thId);
		stack->setFp(0);
		return stack;	
	}
	
	int threadId(){return (int)field(kThreadIdOffset);}
	void setThreadId(int thId){ setField(kThreadIdOffset,(Pointer*)thId);}
		
	Frame* fp() {return (Frame*)field(kFramePointerOffset); }

	void setFp(Frame* fp) {setField(kFramePointerOffset, fp); }

	/*
	void push(Pointer* value) {
		//incsp does the overflowcheck
		Frame* f = fp();
		f->incSp();
		*(Pointer**)f->sp() = value;
	}*/
	
	void pushRef(Pointer* value) {
		//incsp does the overflowcheck
		Frame* f = fp();
		f->incSp();
		*(Pointer**)f->sp() = value;
		*(int*)(f->sp()+kPointerSize) = 1;
	}
	void pushPrim(Pointer* value) {
		//incsp does the overflowcheck
		Frame* f = fp();
		f->incSp();
		*(Pointer**)f->sp() = value;
		*(int*)(f->sp()+kPointerSize) = 0;
	}
	
	void pushDbl(double value){
		pushLong(*(u8*)&value);
	}
	void pushLong(u8 value){
		pushPrim((Pointer*)(value>>32));
		pushPrim((Pointer*)value);
	}
	
	Pointer* peek() {
		return *(Pointer**)fp()->sp();
	}
	bool isTopRef(){
		return *(bool*)(fp()->sp()+kPointerSize);
	}
	/*
	Pointer* pop() {
		// decSp does the underflow check
		Frame* f = fp();
		Pointer** top = (Pointer**)f->sp();
		f->decSp();
		return *top;
	}*/
	
	Pointer* popRef() {
		return fp()->pop();
	}
	
	Pointer* popPrim() {
		return fp()->pop();
	}
	
	u8 popU8(){
		u4 l = (u4)popPrim();
		u8 h = (u8)popPrim();
		return (h<<32)|l;
	}
	long long popLong(){
		return(long long)popU8();
	}
	double popDbl(){
		u8 h = popU8();
		return *((double*)&h);
	}
	
	void popOffFrame(){
		setFp(fp()->getprevFP());
	}
	
	Frame* pushOnFrame(JMethod* method, TaggedArray* locals){
		Frame* f = Frame::Initialize(method);
		f->setLocals(locals);
		//f->setCode(method->getCode());
		//f->setType(method->getContainingType());
		//f->setMethod(method);
		
		f->setprevFP(fp());
		setFp(f);
		
		return f;
	}
	
	Frame* activateFrame(JMethod* method, bool inst ){
		
		//StaticArray* locals = StaticArray::Initalize(method->getMaxLocals());
		TaggedArray* locals = TaggedArray::Initalize(method->getMaxLocals());
		locals->setArrayType((HeapObject*)Smi::fromInt(IType_Array));
		
		//todo: move!!, and make nicer
		//resolve signature
		String* _type = (String*)method->getReturnType();
		const char* _typeStr = _type->str();
		int par = 0;
		for(int p=1;_typeStr[p] != ')';p++){
			if(_typeStr[p] == 'I' || _typeStr[p] == 'L')
				par++;
		}
		
		if(inst) par++;//if it is an instace then there is an extra one on the stack (this).
		dout(debug_bytecodes,"params %i ",par);
		for(;par>0;par--){
			if((int)peek() == 0 ||(inst && par ==1))
				locals->setRef(par-1,popRef());
			else
				locals->setPrim(par-1, popPrim());
		}
		
		if(inst && locals->get(0) == NULL){
			printf("NullPointerException");
			exit(1);
		}

		return pushOnFrame(method, locals);
	}
	
	//c	
	byte getnextbyte(){
		Frame* f = fp();
		if(f == NULL) return 0xFF;
		int pc = f->getPC();
		ByteArray* bc = f->getCode();
		
		if(pc >= bc->array_length()){
			if(f->getprevFP() != NULL){
				//pop off frame, if prev is not null
				popOffFrame();
				return getnextbyte();//and get the next byte from the old frame.
			}
			return 0xFF; //else it is the end and we'll signal the interpreter to stop.
		}
		
		byte b =  bc->get(pc);
		fp()->setPC(pc + 1);
		return b;
	}
	//c
	u4 getnextU4(){
		byte idx1 = getnextbyte();
		byte idx2 = getnextbyte();
		return (idx1 << 8)|idx2;
	}
	
	int stack_size() { return kHeaderSize;}
	void stack_scan(Pointer** nHP){	
		dout(debug_gc,"scan stack old fp %p ",fp());
		if(fp() != NULL){
			//write offset info to be able to restore the sp.
			int offset = fp()->sp() - (Pointer*)fp()->address();
			dout(debug_gc,"offset %i ",offset);
			fp()->setSp( (Pointer*)offset);
			setFp((Frame*)fp()->forward(nHP));
		}
		dout(debug_gc,"new fp %p\n",fp());
	}
	
};

class MethodArea : public HashTable{
public:
	static MethodArea* Initialize(int capacity){
		MethodArea* m = (MethodArea*)HashTable::Initialize(capacity);
		m->setInternalType(Smi::fromInt(IType_MethodArea));
		return m;
	}
	
	JType* lookupType(String* fqName){
		return (JType*)lookup(fqName);
	}
	void insertType(JType* type){
		set(type->getName(), type);
	}
	
	int methodarea_size(){return ((HashTable*)this)->hashtable_size();}
	void methodarea_scan(Pointer** nHP){ dout(debug_gc,"scan methodarea\n",0)((HashTable*)this)->hashtable_scan(nHP);}
};


#endif
